package worker

import (
	"fmt"
	"study-crontab/crontab/common"
	"time"
)

// 任务调度
type Scheduler struct {
	jobEventChan    chan *common.JobEvent              // etcd任务事件队列
	jobPlanMap      map[string]*common.JobSchedulePlan // 任务调度计划表
	jobExecutingMap map[string]*common.JobExecuteInfo  // 任务执行表
	jobResultChan   chan *common.JobExecuteResult
}

var G_scheduler *Scheduler

func NewScheduler() error {
	G_scheduler = &Scheduler{
		jobEventChan:    make(chan *common.JobEvent, 1000),
		jobPlanMap:      make(map[string]*common.JobSchedulePlan),
		jobExecutingMap: make(map[string]*common.JobExecuteInfo),
		jobResultChan:   make(chan *common.JobExecuteResult, 1000),
	}
	//启动调度协程
	go G_scheduler.scheduleLoop()
	return nil
}

//调度协程
func (s *Scheduler) scheduleLoop() {
	var (
		jobEvent  *common.JobEvent
		jobResult *common.JobExecuteResult
	)
	//初始化调度
	scheduleAfter := s.TrySchedule()

	//调度的延时定时器
	scheduleTimer := time.NewTimer(scheduleAfter)
	//定时任务
	for {
		select {
		case jobEvent = <-s.jobEventChan: //监听任务变化事件
			//对内存维护的任务列表做增删改查
			s.handleJobEvent(jobEvent)
		case <-scheduleTimer.C: //最近的任务到期
		case jobResult = <-s.jobResultChan: //监听任务执行结果
			s.handleJobResult(jobResult)
		}
		//调度一次任务
		scheduleAfter = s.TrySchedule()
		//重置调度间隔
		scheduleTimer.Reset(scheduleAfter)
	}
}

//推送任务变化事件
func (s *Scheduler) PushJobEvent(jobEvent *common.JobEvent) {
	s.jobEventChan <- jobEvent
}

//处理事件
func (s *Scheduler) handleJobEvent(jobEvent *common.JobEvent) {
	var (
		jobSchedulePlan *common.JobSchedulePlan
		err             error
	)
	switch jobEvent.EventType {
	case common.JOB_EVENT_SAVE: //保存任务事件
		if jobSchedulePlan, err = common.BuildJobSchedulePlan(jobEvent.Job); err != nil {
			return
		}
		s.jobPlanMap[jobEvent.Job.Name] = jobSchedulePlan
	case common.JOB_EVENT_DELETE: //删除任务事件
		if _, ok := s.jobPlanMap[jobEvent.Job.Name]; ok {
			delete(s.jobPlanMap, jobEvent.Job.Name)
		}
	case common.JOB_EVENT_KILLER: //杀死任务事件
		//判断任务是否在执行中
		info, ok := s.jobExecutingMap[jobEvent.Job.Name]
		if ok {
			fmt.Println("杀死任务")
			info.CancelFunc()
			if _, ok := s.jobPlanMap[jobEvent.Job.Name]; ok {
				fmt.Println("停止cronjob")

				delete(s.jobPlanMap, jobEvent.Job.Name)
			}
		}
	}
}

//处理结果
func (s *Scheduler) handleJobResult(jobResult *common.JobExecuteResult) {
	delete(s.jobExecutingMap, jobResult.ExecuteInfo.Job.Name)

	//生成执行日志
	if jobResult.Err != common.ERR_LOCK_ALREADY_REQUIRED {
		jobLog := &common.JobLog{
			JobName:   jobResult.ExecuteInfo.Job.Name,
			Command:   jobResult.ExecuteInfo.Job.Command,
			Output:    string(jobResult.Output),
			PlanTime:  jobResult.ExecuteInfo.PlanTime.UnixMilli(),
			RealTime:  jobResult.ExecuteInfo.RealTime.UnixMilli(),
			StartTime: jobResult.StartTime.UnixMilli(),
			EndTime:   jobResult.StartTime.UnixMilli(),
		}
		if jobResult.Err != nil {
			jobLog.Err = jobResult.Err.Error()
		} else {
			jobLog.Err = ""
		}
		G_logSink.Append(jobLog)
	}
	fmt.Println("任务执行完成:", jobResult.ExecuteInfo.Job.Name, string(jobResult.Output), jobResult.Err)
}

//计算任务调度状态
func (s *Scheduler) TrySchedule() (scheduleAfter time.Duration) {
	var nearTime *time.Time
	if len(s.jobPlanMap) == 0 {
		scheduleAfter = 1 * time.Second
		return
	}
	//当前时间
	now := time.Now()
	//1.遍历所有任务
	for _, jobPlan := range s.jobPlanMap {
		if jobPlan.NextTime.Before(now) || jobPlan.NextTime.Equal(now) {
			//尝试执行任务
			s.TryStartJob(jobPlan)
			//更新下次执行时间
			jobPlan.NextTime = jobPlan.Expr.Next(now)
		}
		//统计最近一个要过期的任务时间
		if nearTime == nil || jobPlan.NextTime.Before(*nearTime) {
			nearTime = &jobPlan.NextTime
		}
	}
	//下次调度间隔 (最近要执行的任务调度时间 - 当前时间)
	scheduleAfter = (*nearTime).Sub(now)
	return
}

//尝试执行任务
func (s *Scheduler) TryStartJob(jobPlan *common.JobSchedulePlan) {
	//调度 和 执行 是2件事情

	//执行的任务可能允许很久，1分钟会调度60次，但是只能执行一次
	//如果任务正在执行,跳过本次调度
	if _, ok := s.jobExecutingMap[jobPlan.Job.Name]; ok {
		fmt.Println(jobPlan.Job.Name, "任务正在执行，跳过执行")
		return
	}

	jobExecuteInfo := common.BuildJobExecuteInfo(jobPlan)

	//保存执行状态
	s.jobExecutingMap[jobPlan.Job.Name] = jobExecuteInfo

	//执行任务
	fmt.Println("执行任务:", jobExecuteInfo.Job.Name, jobExecuteInfo.PlanTime, jobExecuteInfo.RealTime)
	G_Executor.ExecutorJob(jobExecuteInfo)

	return
}

//回传任务执行结果
func (s *Scheduler) PushJobResult(jobResult *common.JobExecuteResult) {
	s.jobResultChan <- jobResult
}
